<?php

namespace App\Service\PayApi;

use App\Constants\SettleAccessType;
use App\Model\Dao\ChannelInfoDao;
use App\Model\Dao\MchAccountDao;
use App\Model\Dao\MchConfigDao;
use App\Model\Dao\MchInfoDao;
use App\Model\Dao\TradeOrderDetailDao;
use App\Model\Enum\EmailSendTypeEnum;
use App\Model\Enum\OrderTransStatusEnum;
use App\Model\Enum\PayTypeEnum;
use App\Service\EmailSendInfoService;
use App\Service\MchAccountStatusService;
use App\Service\MchConfigStatusService;
use App\Service\MchInfoStatusService;
use App\Service\SettleService;
use App\Service\SysCardTypeInfoService;
use App\Utils\CustomUtils;
use App\Utils\SignUtils;
use App\Utils\StringUtils;
use Hyperf\DbConnection\Db;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Arr;
use phpDocumentor\Reflection\Utils;

/**
 * Class PayService
 * 商户API支付
 */
class PayService {

    /**
     * @var MchInfoDao
     * @Inject()
     */
    protected $MchInfoDao;

    /**
     * @var MchConfigDao
     * @Inject()
     */
    protected $MchConfigDao;

    /**
     * @var ChannelInfoDao
     * @Inject()
     */
    protected $ChannelInfoDao;

    /**
     * @var SettleService
     * @Inject()
     */
    protected $SettleService;

    /**
     * @var MchAccountDao
     * @Inject()
     */
    protected $MchAccountDao;

    /**
     * @var TradeOrderService
     * @Inject()
     */
    protected $TradeOrderService;

    /**
     * @var TradeOrderDetailDao
     * @Inject()
     */
    protected $TradeOrderDetailDao;

    /**
     * @var BankPayService
     * @Inject()
     */
    protected $BankPayService;

    /**
     * 创建预支付订单
     *
     * @param $params
     *
     * @throws \Throwable
     */
    public function createPreOrder ($params) {
        CustomUtils::xzxDebug('创建预支付订单');
        return $this->TradeOrderService->createPreOrder($params);
    }

    /**
     * 获取预支付订单
     *
     * @param $params
     *
     * @return array
     * @throws \Exception
     */
    public function getPreOrder ($params) {
        CustomUtils::xzxDebug('获取预支付订单');
        return $this->TradeOrderService->getPreOrder($params);
    }

    /**
     * 支付预支付订单
     *
     * @param $mch
     * @param $order
     * @param $payType
     * @param $payAccount
     *
     * @return array
     * @throws \Throwable
     */
    public function payPreOrder ($mch, $order, $payType, $payAccount) {

        CustomUtils::xzxDebug('支付预支付订单');

        $mchInfo    = $mch['mchInfo'];
        $mchAccount = $mch['mchAccount'];
        $mchConfig  = $mch['mchConfig'];

        $orderDetail = $order['orderDetail'];
        $orderBase   = $order['orderBase'];

        $orderId = $orderDetail['id'];

        if ($orderDetail['transStatus'] != OrderTransStatusEnum::INIT) {
            throw new \Exception(trans('pay.order.status.traded'));
        }

        // 补充支付类型
        $update = ['channelType' => $payType];
        $this->TradeOrderDetailDao->update($orderId, $update);
        $orderDetail          = array_merge($orderDetail, $update);
        $order['orderDetail'] = $orderDetail;
        unset($update);

        // 补充支付信息
        $orderReceive = $this->TradeOrderService->setOrderReceive($payType, $orderId, array_merge($payAccount, [
            'tradeNo' => $orderDetail['tradeNo'],
            'mchId'   => $orderDetail['mchId'],
            'mchNo'   => $orderDetail['mchNo'],
        ]));

        $order['orderReceive'] = $orderReceive;

        try {
            $resultPay = $this->_pay($mch, $order, $payType, $payAccount);
        } catch (\Throwable $e) {
            throw $e;
        } finally {
            $this->_onPayDone($mch, $order, $payType, $payAccount);
        }

        // 构造返回参数并返回
        $data = [
            'mchNo'            => $orderDetail['mchNo'],
            'orderNo'          => $orderDetail['orderNo'],
            'tradeNo'          => $orderDetail['tradeNo'],
            'mchTransCurrency' => $orderDetail['mchTransCurrency'],
            'mchTransAmount'   => $orderDetail['mchTransAmount'],
            'transStatus'      => $orderDetail['transStatus'],
        ];

        $data['sign']       = SignUtils::makeSign($data, $mchInfo['appSecret']);
        $data['paymentUrl'] = isset($resultPay['bank']['paymentUrl']) ? $resultPay['bank']['paymentUrl'] : '';

        return CustomUtils::buildSuccess($data);
    }

    /**
     * 支付完成后收尾处理(通知结算系统)
     *
     * @param $mch
     * @param $order
     * @param $payType
     * @param $payAccount
     */
    private function _onPayDone ($mch, $order, $payType, $payAccount) {

        $mchInfo    = $mch['mchInfo'];
        $mchAccount = $mch['mchAccount'];
        $mchConfig  = $mch['mchConfig'];

        $orderDetail  = $order['orderDetail'];
        $orderBase    = $order['orderBase'];
        $orderReceive = $order['orderReceive'];

        $orderId = $orderDetail['id'];

        // 更新订单详情
        $orderDetail = $this->TradeOrderService->getOrderDetail($orderId);

        // 交易结算
        if ($orderDetail['transStatus'] == OrderTransStatusEnum::BANK_CALLBACK_SUCCESS) {
            // 交易成功
            $this->SettleService->addMq($mchInfo['id'], [
                'accessType' => SettleAccessType::ACCESS_TYPE_TRADE_ORDER,
                'orderId'    => $orderDetail['id'],
            ]);
        }
        else if ($orderDetail['transStatus'] == OrderTransStatusEnum::BANK_SEND_ERROR) {
            // 交易失败
            $this->SettleService->addMq($mchInfo['id'], [
                'accessType' => SettleAccessType::ACCESS_TYPE_TRADE_FAIL,
                'orderId'    => $orderDetail['id'],
            ]);
        }
    }

    /**
     * 筛选通道->计算币种汇率->风控调度->网关支付
     *
     * @param $mch
     * @param $order
     * @param $payType
     * @param $payAccount
     *
     * @return array
     * @throws \Throwable
     */
    private function _pay ($mch, $order, $payType, $payAccount) {

        $mchInfo    = $mch['mchInfo'];
        $mchAccount = $mch['mchAccount'];
        $mchConfig  = $mch['mchConfig'];

        $orderDetail  = $order['orderDetail'];
        $orderBase    = $order['orderBase'];
        $orderReceive = $order['orderReceive'];

        // 通道列表获取
        $channelList = $this->ChannelInfoDao->getMchChannelList($orderDetail['mchNo'], $payType);

        // 币种金额计算
        $re             = $this->_calcCurrencyAndAmount($mchConfig, $channelList, $orderDetail);
        $mchToPlatform  = $re['mchToPlatform'];
        $platformToBank = $re['platformToBank'];

        // 将计算得到的商户结算金额和币种存起来
        $this->TradeOrderDetailDao->update($orderDetail['id'], [
            'settleCurrency' => $mchToPlatform['platformCurrency'],
            'settleAmount'   => $mchToPlatform['platformTransAmount'],
        ]);

        // 风控调度
        $riskData        = $this->_riskAndDispatch($mchInfo, $orderDetail, $orderBase, $orderReceive, $platformToBank);
        $dispatchChannel = $riskData['channel'];

        // 记录汇率
        $transRate                  = [];
        $transRate['mchToPlatform'] = $mchToPlatform['platformTransRate'];
        foreach ($platformToBank as $value) {
            if ($value['channelId'] == $dispatchChannel['channelId']) {
                $transRate['platformToBank'] = $value['channelTransRate'];
                break;
            }
        }
        $this->TradeOrderDetailDao->update($orderDetail['id'], [
            'transRate' => json_encode($transRate, JSON_UNESCAPED_UNICODE),
        ]);

        // 上送银行网关
        $reBankPay = $this->BankPayService->handle($mch, $order, $payType, $payAccount, $dispatchChannel);

        if (CustomUtils::isError($reBankPay)) {
            throw new \Exception($reBankPay['message']);
        }

        $data = [
            'risk' => $riskData,
            'bank' => $reBankPay['data'],
        ];

        return $data;
    }

    /**
     * 币种金额计算
     *
     * @param $mch
     * @param $channelList
     * @param $orderDetail
     *
     * @return mixed
     * @throws \Throwable
     */
    protected function _calcCurrencyAndAmount ($mch, $channelList, $orderDetail) {

        $bankResult = null;

        try {
            $bankResult = ApplicationContext::getContainer()->get(PayCurrencyAndAmountService::class)->calcCurrencyAndAmount($mch, $channelList, $orderDetail);
        } catch (\Throwable $e) {
            // 更新订单状态为业务错误
            ApplicationContext::getContainer()->get(TradeOrderService::class)->setOrderBaseFail($orderDetail['tradeNo'], $e->getCode(), $e->getMessage());
            throw $e;
        }

        return $bankResult;
    }

    /**
     * 风控调度
     *
     * @param $mch
     * @param $orderDetail
     * @param $orderBase
     * @param $orderReceive
     * @param $platformToBank
     *
     * @return mixed
     * @throws \Exception
     * @throws \Throwable
     */
    protected function _riskAndDispatch ($mch, $orderDetail, $orderBase, $orderReceive, $platformToBank) {
        return ApplicationContext::getContainer()->get(RiskAndDispatchService::class)->riskAndDispatch($mch, $orderDetail, $orderBase, $orderReceive, $platformToBank);
    }
}
